//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <winhack.h>
#include <elasys_server.h>
#include <driver.h>
#include <sdl.h>
#include <stdio.h>
#include <msgqueue.h>
#include <sysconfig.h>
#include <framebuffer.h>
#include "calibrate.h"

#define MINORLCDRESPONSE    1       //Response for mouse touch in MajorLCD
#define MAJORLCDRESPONSE    2       //Response for mouse touch in MinorLCD

EXTERN_C void __stdcall _SysCallNextHook(int hHook, int nCode, void *pParam);

extern ULONG g_VideoPowerMode;
void InputEventNotify(int eType, int nCode, void *pParam)
{
    ULONG PreMode = g_VideoPowerMode;
    _SysCallNextHook(30,0,&PreMode);
    if (PreMode == 0/*LCD Power Off*/)return;//ignore first input event

    //printf("InEv[%d]: nCode(%d) %x\n", eType, nCode, pParam?*(int *)pParam:0);
    _SysCallNextHook(HOOK_INPUTEVENT, nCode, pParam);
    NotifyByIsr(EVENT_KEYSOUND, eType, pParam);
    EventNotify(eType, nCode, pParam);
}

EXTERN_C int GetSoftKey(int x, int y, KeyType type);
extern VideoContext g_videoCtx;

static Boolean PtInLCD(int x, int y)
{
    int LcdPosX = g_videoCtx.nLCDPosX;
    int LcdPosY = g_videoCtx.nLCDPosY;
    int LCDWidth = g_videoCtx.nLCDWidth;
    int LCDHeight = g_videoCtx.nLCDHeight;

    if (g_videoCtx.bDoubleScreen) {
        LcdPosX = g_videoCtx.nMajorLCDPosX;
        LcdPosY = g_videoCtx.nMajorLCDPosY;
        LCDWidth = g_videoCtx.nMajorLCDWidth;
        LCDHeight = g_videoCtx.nMajorLCDHeight;
    }

    if (x >= LCDWidth + LcdPosX || x < LcdPosX \
        || y >= LCDHeight + LcdPosY || y < LcdPosY)
        return FALSE;

    return TRUE;
}

/* Whether touch point is in MinorLCD */
static Boolean PtInMinorLCD(int x, int y)
{
    int MinorLCDWidth = g_videoCtx.nMinorLCDWidth;
    int MinorLCDHeight = g_videoCtx.nMinorLCDHeight;
    int MinorLcdPosX = g_videoCtx.nMinorLCDPosX;
    int MinorLcdPosY = g_videoCtx.nMinorLCDPosY;

    if (x >= MinorLCDWidth + MinorLcdPosX || x < MinorLcdPosX \
        || y >= MinorLCDHeight + MinorLcdPosY || y < MinorLcdPosY) {
        return FALSE;
    }

    return TRUE;
}

extern unsigned long ElaSerialID;
int g_bInCalibrate = 0;
int g_bCalibrateEnable = 0;
HANDLE g_CaliEvent;

static TOUCH_DATA TouchInfo = {0, 0, 0};
static BOOL bPenPush = FALSE;

UINT g_PenTimerID = 0;
EXTERN_C UINT _StartTimer(UINT uSeconds, void * pTimerProc, UINT uType);
EXTERN_C void _StopTimer(UINT wTimerID);
__inline void StopPenTimer()
{
    if (g_PenTimerID) {
        _StopTimer(g_PenTimerID);
        g_PenTimerID = 0;
    }
}

void __stdcall OnMouseDownTimerOut(UINT wTimerID, UINT msg,
    DWORD dwUser, DWORD dw1, DWORD dw2)
{
    StopPenTimer();

    if (TouchInfo.dir == 1/*down*/) {
        //Insert pen push event
        TouchInfo.dir = 3/*push*/;
        bPenPush = TRUE;
        printf("Touch %d: (%d, %d)\n", TouchInfo.dir, TouchInfo.x, TouchInfo.y);
        InputEventNotify(EVENT_TOUCH, 1, &TouchInfo);
    }
}

__inline void StartPenTimer(UINT uSec)
{
    if (g_PenTimerID) StopPenTimer();
    g_PenTimerID = _StartTimer(uSec, (void *)OnMouseDownTimerOut, 0);
}

void TouchNotify(int dir, int x, int y)
{
    //int ox = x, oy = y;
    if (dir == 1/*down*/) {
        StartPenTimer(1/*seconds*/);
    }
    else {
        StopPenTimer();
    }

    int X = x, Y = y;

    if (g_bCalibrateEnable) {//config setting
        x += (ElaSerialID >> 8) & 0x1f;
        y += (ElaSerialID >> 0) & 0x1f;
    }

#if defined(_DBGCALIBRATE)
    printf("%d: (%d, %d)\t-> (%d, %d)\n", dir, x, y, X, Y);

    // test calibrate
    static int bCalibrate = 0;
    if (!bCalibrate) {
        bCalibrate = 1;

        RefPoint P[2];
        P[0].x = 145;
        P[0].y = 63;
        P[0].X = 128;
        P[0].Y = 63;

        P[1].x = 155;
        P[1].y = 66;
        P[1].X = 138;
        P[1].Y = 66;
        SetCalibrationMatrix(2, P);
    }
#endif

    if (g_bCalibrateEnable) {//config setting
        if (!g_bInCalibrate)
            GetDisplayPoint(x, y, &X, &Y);
        else {
            X = x;
            Y = y;
        }
        //printf("==== (%d, %d)\t-> (%d, %d)\t-> (%d, %d)\n", ox, oy, x, y, X, Y);
    }

    TouchInfo.dir = (short)dir;
    TouchInfo.x = (short)X;
    TouchInfo.y = (short)Y;

    //Check if pen push before
    if (dir == 1/*down*/ || dir == 0/*up*/) {
        if (dir == 0/*up*/ && bPenPush == TRUE) TouchInfo.dir = 8/*push_up*/;
        bPenPush = FALSE;
    }

    if (TouchInfo.dir == 8/*push_up*/) {
        printf("Touch %d: (%d, %d)\n", TouchInfo.dir, TouchInfo.x, TouchInfo.y);
    }

    InputEventNotify(EVENT_TOUCH, 1, &TouchInfo);
}

//Func for GSensor
typedef void (*COORDINATE)(int x, int y, int *px, int *py);
COORDINATE g_Coordinate;
EXTERN HANDLE g_hGsensorLock;

void CoordinateUp(int x, int y, int *px, int *py)
{
    *px = x;
    *py = y;
}

void CoordinateDown(int x, int y, int *px, int *py)
{
    *px = g_videoCtx.nMobileWidth - x;
    *py = g_videoCtx.nMobileHeight - y;
}

void CoordinateRight(int x, int y, int *px, int *py)
{
    *px = y;
    *py = g_videoCtx.nMobileHeight - x;
}

void CoordinateLeft(int x, int y, int *px, int *py)
{
    *px = g_videoCtx.nMobileWidth - y;
    *py = x;
}

// Insert touch events into the message queue
EXTERN_C int TouchScreen(SDL_Event *pEvent, TouchType type)
{
    static BOOL s_bMousePressState = FALSE;
    int x, y;
    int offsetX = 0;
    int offsetY = 0;           //Offset of x, y
    Boolean bInLCD = FALSE;         //If mouse click in LCD

    WaitForSingleObject(g_hGsensorLock, (DWORD)INFINITE);
    g_Coordinate(pEvent->button.x,
                 pEvent->button.y,
                 &x, &y);
    ReleaseMutex(g_hGsensorLock);

    if (!g_videoCtx.bDoubleScreen) {
        if (PtInLCD(x, y)) {
            offsetX = (x - g_videoCtx.nLCDPosX);
            offsetY = (y - g_videoCtx.nLCDPosY);
            bInLCD = TRUE;
        }
    }
    else {
        if (MAJORLCDRESPONSE == (g_videoCtx.nTouchResponseLCD & MAJORLCDRESPONSE)
            && PtInLCD(x, y)) {
            offsetX = (int)((x - g_videoCtx.nMajorLCDPosX) /
                            g_videoCtx.fMajorShrinkRateX);
            offsetY = (int)((y - g_videoCtx.nMajorLCDPosY) /
                            g_videoCtx.fMajorShrinkRateY);
            bInLCD = TRUE;
        }
        if (MINORLCDRESPONSE == (g_videoCtx.nTouchResponseLCD & MINORLCDRESPONSE)
            &&PtInMinorLCD(x, y)) {
            offsetX = (int)((x - g_videoCtx.nMinorLCDPosX) /
                             g_videoCtx.fMinorShrinkRateX);
            offsetY = (int)((y - g_videoCtx.nMinorLCDPosY) /
                             g_videoCtx.fMinorShrinkRateY +
                             g_videoCtx.nMajorLCDHeight / g_videoCtx.fMajorShrinkRateY);
            bInLCD = TRUE;
        }
    }

    switch (type) {
        case MOUSEMOTION:{
            if (!s_bMousePressState) break;
            if (bInLCD) {
                TouchNotify(2, offsetX, offsetY);   /*move*/
            }
            else {
                _SysCallNextHook(25, 0, 0);
            }
            break;
        }
        case MOUSEBUTTONUP:
            s_bMousePressState = FALSE;
            if (bInLCD) {
                TouchNotify(0, offsetX, offsetY);   /*up*/
            }
            else {
                GetSoftKey(x, y, KEYUP);
            }
            break;

        case MOUSEBUTTONDOWN:
            s_bMousePressState = TRUE;
            if (bInLCD) {
                TouchNotify(1, offsetX, offsetY);   /*down*/
            }
            else {
                GetSoftKey(x, y, KEYDOWN);
            }
            break;

        default:
            break;
    }
    return 0;
}

void InitMouse()
{
    assert(6 == sizeof(TOUCH_DATA));
    ConfigGetItemInt(L"CalibrateEnable", &g_bCalibrateEnable);
    g_CaliEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
    g_Coordinate = CoordinateUp;
}

void DeleMouse()
{
    CloseHandle(g_CaliEvent);
    g_CaliEvent = NULL;
}

void EnQueueProc(int eType, int nCode, void *pParam);
void OnMouseDownProc(int eType, int nCode, void *pParam)
{
    if (TouchInfo.dir != 1/*down*/) return;

    //printf("~~~~~~~~[%d]: nCode(%d) %x\n", eType, nCode, pParam?*(int *)pParam:0);
    TouchInfo.dir = 4/*CaliOK*/;
    SetEvent(g_CaliEvent);

    return ;
}

void OnKeyCancelProc(int eType, int nCode, void *pParam)
{
    //printf("~~~~~~~~[%d]: nCode(%d) %x\n", eType, nCode, pParam?*(int *)pParam:0);
    //KPD_DATA *pKey = (KPD_DATA *)pParam;
    //if (pKey->keycode == 0x89/*VK_PHONE_HANG_UP*/)
    {
        SetEvent(g_CaliEvent);
    }
    return ;
}

BOOL SetCalibrate(RefPoint *P)
{
    BOOL ret = FALSE;
    //Hook Touch & Keypad Event
    AddNotify(EVENT_TOUCH, (void*)OnMouseDownProc);
    AddNotify(EVENT_KEYPAD, (void*)OnKeyCancelProc);

    printf("<<<<<<<<<%d\n", g_bInCalibrate);
    g_bInCalibrate = 1;

    WaitForSingleObject(g_CaliEvent, (DWORD)INFINITE);//wait for MouseDown or KeyCancel

    P->x = P->y = P->X = P->Y = 0;
    if (TouchInfo.dir == 4/*CaliOK*/) {
        P->x = (int)TouchInfo.x;
        P->y = (int)TouchInfo.y;
        ret = TRUE;
    }

    g_bInCalibrate = 0;

    //Restore Touch & Keypad Event Hook
    AddNotify(EVENT_TOUCH, (void*)EnQueueProc);
    AddNotify(EVENT_KEYPAD, (void*)EnQueueProc);

    return ret;
}
